package com.jzo2o.market.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.RedisConstants;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CountResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
import static com.jzo2o.market.enums.ActivityStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;
    @Resource
    private CouponMapper couponMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdateActivity(ActivitySaveReqDTO activitySaveReqDTO) {
        //校验请求参数
        activitySaveReqDTO.check();
        //补充数据
        Activity activity = new Activity();
        BeanUtils.copyProperties(activitySaveReqDTO, activity);
        activity.setStatus(ActivityStatusEnum.NO_DISTRIBUTE.getStatus());
        activity.setStockNum(activitySaveReqDTO.getTotalNum());
        this.saveOrUpdate(activity);
    }

    @Override
    public PageResult<ActivityInfoResDTO> findByPage(ActivityQueryForPageReqDTO dto) {
        //1. 创建分页条件
        Page<Activity> page = this.lambdaQuery()
                .eq(dto.getId() != null, Activity::getId, dto.getId())
                .like(StringUtils.isNotEmpty(dto.getName()), Activity::getName, dto.getName())
                .eq(dto.getType() != null, Activity::getType, dto.getType())
                .eq(dto.getStatus() != null, Activity::getStatus, dto.getStatus())
                .page(new Page<>(dto.getPageNo(), dto.getPageSize()));
        //判断查询结果是否为空
        if (CollUtil.isEmpty(page.getRecords())) {
            return new PageResult<>(0L, 0L, new ArrayList<>());
        }
        //收集所有的优惠活动id
        List<Long> ids = page.getRecords().stream().map(e -> e.getId()).collect(Collectors.toList());
        //查询优惠券核销数量
        Map<Long, List<CouponWriteOff>> map = couponWriteOffService.lambdaQuery()
                .in(CouponWriteOff::getActivityId, ids)
                .list()
                .stream()
                .collect(Collectors.groupingBy(CouponWriteOff::getActivityId));
        //查询优惠券领取数量
        Map<Long, List<Coupon>> rmap = couponService.lambdaQuery()
                .in(Coupon::getActivityId, ids)
                .list()
                .stream()
                .collect(Collectors.groupingBy(Coupon::getActivityId));
        //转换响应结果
        List<ActivityInfoResDTO> collect = page.getRecords().stream().map(e -> {
            ActivityInfoResDTO activityInfoResDTO = BeanUtils.copyProperties(e, ActivityInfoResDTO.class);
            //补充数据
            //补充核销数
            activityInfoResDTO.setWriteOffNum(map.getOrDefault(e.getId(), List.of()).size());
            //补充领取量
            activityInfoResDTO.setReceiveNum(rmap.getOrDefault(e.getId(), List.of()).size());
            return activityInfoResDTO;
        }).collect(Collectors.toList());
        return new PageResult<>(page.getPages(), page.getTotal(), collect);
    }

    @Override
    public ActivityInfoResDTO findById(Long id) {
        Activity activity = this.getById(id);
        if (ObjectUtils.isEmpty(activity)) {
            throw new ForbiddenOperationException("该活动不存在");
        }
        ActivityInfoResDTO activityInfoResDTO = BeanUtils.copyProperties(activity, ActivityInfoResDTO.class);
        activityInfoResDTO.setWriteOffNum(couponWriteOffService.lambdaQuery().eq(CouponWriteOff::getActivityId, id).count());
        activityInfoResDTO.setReceiveNum(couponService.lambdaQuery().eq(Coupon::getActivityId, id).count());
        return activityInfoResDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long id) {
        //查询该优惠活动信息
        Activity activity = this.getById(id);
        //修改活动状态
        boolean update = this.lambdaUpdate()
                .eq(Activity::getId, id)
                .in(Activity::getStatus, NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())
                .set(Activity::getStatus, VOIDED.getStatus())
                .update();
        if (update) {
            //终止成功
            couponService.lambdaUpdate()
                    .eq(Coupon::getActivityId, id)
                    .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                    .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus())
                    .update();
        }
    }

    @Override
    public void updateStatus() {
        LocalDateTime now = LocalDateTime.now();
        this.lambdaUpdate()
                .eq(Activity::getStatus, NO_DISTRIBUTE.getStatus())
                .le(Activity::getDistributeStartTime, now)
                .gt(Activity::getDistributeEndTime, now)
                .set(Activity::getStatus, DISTRIBUTING.getStatus())
                .update();
        this.lambdaUpdate()
                .in(Activity::getStatus, Arrays.asList(DISTRIBUTING.getStatus(), NO_DISTRIBUTE.getStatus()))//检索待生效及进行中的活动
                .lt(Activity::getDistributeEndTime, now)
                .set(Activity::getStatus, LOSE_EFFICACY.getStatus())
                .update();
    }

    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        //1. 缓存中获取活动信息
        String jsonString = (String) redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (StringUtils.isEmpty(jsonString)) {
            return null;
        }

        //2. 字符串转换为集合
        List<ActivityInfoResDTO> activityInfoResDTOList = JSON.parseArray(jsonString, ActivityInfoResDTO.class);
        if (CollUtil.isEmpty(activityInfoResDTOList)) {
            return null;
        }

        //3. 过滤出指定id的活动
        return activityInfoResDTOList.stream()
                .filter(e -> e.getId().equals(id))
                .findFirst().orElse(null);
    }

    //扣减库存
    @Override
    public void deductStock(Long id) {
        boolean update = lambdaUpdate()
                .setSql("stock_num = stock_num-1")
                .eq(Activity::getId, id)
                .gt(Activity::getStockNum, 0)
                .update();
        if (!update) {
            throw new CommonException("扣减优惠券库存失败，活动id:" + id);
        }
    }

    @Override
    public void preHeat() {
        //查询进行中还未到结束的优惠券活动， 1个月内待开始的优惠券活动
        List<Activity> list = this.lambdaQuery()
                .le(Activity::getDistributeStartTime, LocalDateTime.now().plusDays(30))//1个月内即将开始的
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), ActivityStatusEnum.DISTRIBUTING.getStatus()))//查询待开始和进行中的
                .orderByAsc(Activity::getDistributeStartTime)//发放开始时间正序
                .list();
        if (CollUtils.isEmpty(list)) {
            list = new ArrayList<>();//防止缓存穿透
        }

        // 2.数据转换
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtils.copyToList(list, SeizeCouponInfoResDTO.class);
        String seizeCouponInfoStr = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);

        // 3.活动列表写入缓存
        redisTemplate.opsForValue().set(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST, seizeCouponInfoStr);
        // 将待生效的活动库存写入redis(使用put方法，可以对已设置的记录进行更改)
        list.stream()
                .filter(v -> getStatus(v.getDistributeStartTime(), v.getDistributeEndTime(), v.getStatus()) == 1)
                .forEach(v ->
                        redisTemplate.opsForHash().putIfAbsent(
                                String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, v.getId() % 10), v.getId(), v.getTotalNum()
                        )
                );
        // 对于已生效的活动库存没有同步时再进行同步(对已生效的活动库存使用putIfAbsent实现，当key不存在时才执行设置操作)
        list.stream()
                .filter(v -> getStatus(v.getDistributeStartTime(), v.getDistributeEndTime(), v.getStatus()) == 2)
                .forEach(v ->
                        redisTemplate.opsForHash().putIfAbsent(
                                String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, v.getId() % 10), v.getId(), v.getTotalNum()
                        )
                );
    }

    @Override
    public List<SeizeCouponInfoResDTO> queryForListFromCache(Integer tabType) {
        //1. 从redis中查询出缓存的内容
        String jsonString = (String) redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (StringUtils.isEmpty(jsonString)) {
            return List.of();
        }

        //2. 将查询到的字符串转换为List
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOList
                = JSON.parseArray(jsonString, SeizeCouponInfoResDTO.class);
        if (CollUtil.isEmpty(seizeCouponInfoResDTOList)) {
            return List.of();
        }

        //3. 处理状态
        return seizeCouponInfoResDTOList.stream()
                .filter(e -> {
                    //根据时间处理活动的真实状态
                    int status = getStatus(e.getDistributeStartTime(), e.getDistributeEndTime(), e.getStatus());
                    return status == (tabType == 1 ? DISTRIBUTING.getStatus() : NO_DISTRIBUTE.getStatus());
                }).map(e -> {
                    //处理剩余量字段
                    e.setRemainNum(e.getStockNum());
                    return e;
                }).collect(Collectors.toList());
    }

    //根据时间处理活动的真实状态
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        if (NO_DISTRIBUTE.getStatus() == status && distributeStartTime.isBefore(DateUtils.now()) && distributeEndTime.isAfter(LocalDateTime.now())) {
            //状态在待生效, 但是 活动开始时间<=当前时间<活动结束时间  需要改为 进行中
            return DISTRIBUTING.getStatus();
        } else if (NO_DISTRIBUTE.getStatus() == status && distributeEndTime.isBefore(LocalDateTime.now())) {
            //状态在待生效, 但是 活动结束时间 < 当前时间 需要改为已结束
            return LOSE_EFFICACY.getStatus();
        } else if (DISTRIBUTING.getStatus() == status && distributeEndTime.isBefore(LocalDateTime.now())) {
            //状态在进行中, 但是 活动结束时间 < 当前时间 需要改为已结束
            return LOSE_EFFICACY.getStatus();
        }
        return status;
    }
}